package com.quectel.business.dms.application.access.accessor.ipg;

import com.google.common.base.Preconditions;
import com.quectel.business.common.parking.CarAccessLimitBusiness;
import com.quectel.business.common.parking.ParkingBusiness;
import com.quectel.business.common.parking.ParkingGateCmdBusiness;
import com.quectel.business.common.websocket.WebSocketComponent;
import com.quectel.business.common.websocket.send.SendMsgType;
import com.quectel.business.dms.AccessorUtils;
import com.quectel.business.dms.DubboServiceExportFactory;
import com.quectel.business.dms.application.access.AbstractApplicationAccessor;
import com.quectel.business.dms.application.access.accessor.ipg.model.AuthCarCmd;
import com.quectel.business.dms.application.access.accessor.ipg.model.GuardOpenResp;
import com.quectel.constant.core.PreWarnConstants;
import com.quectel.constant.core.car.CarConstants;
import com.quectel.constant.core.eventrecord.CarAccessRecordConstants;
import com.quectel.constant.core.parking.ParkingOrderConstants;
import com.quectel.core.module.car.dto.CarDto;
import com.quectel.core.module.car.dto.mongo.MongoCarAccessRecordDto;
import com.quectel.core.module.car.service.CarService;
import com.quectel.core.module.car.service.mongo.MongoCarAccessRecordService;
import com.quectel.core.module.device.dto.DeviceDto;
import com.quectel.core.module.device.dto.DeviceTypeDto;
import com.quectel.core.module.parking.dto.*;
import com.quectel.core.module.parking.service.ParkingCurrentCarService;
import com.quectel.core.module.parking.service.ParkingOrderService;
import com.quectel.core.module.parkingcard.dto.ParkingCardMemberDto;
import com.quectel.core.module.parkingcard.service.ParkingCardMemberService;
import com.quectel.core.module.prewarning.dto.PreWarningConfigDto;
import com.quectel.core.module.prewarning.dto.PreWarningRecordDto;
import com.quectel.core.module.prewarning.service.PreWarningConfigService;
import com.quectel.core.module.prewarning.service.PreWarningRecordService;
import com.quectel.util.common.DateUtils;
import com.quectel.util.common.JacksonUtils;
import com.quectel.util.spring.SpringUtils;
import com.quectel.util.thread.ThreadPoolUtils;
import lombok.Data;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 智慧道闸相关能力
 *
 * @author caolu
 * @email louis.cao@quectel.com
 */
public abstract class AbstractIntelligentParkingGateAccessor
        extends AbstractApplicationAccessor {

    private static final ThreadPoolExecutor PARK_EVENT_HANDLE_POOL = ThreadPoolUtils.createCommonIOBoundPool("park-event-handle-pool");

    /**
     * 道闸类设备能力枚举
     */
    public enum AbilityEnum {
        // 车辆授权
        AUTH_CAR,
        // 临停支付
        PARKING_PAY;
    }

    public abstract GuardOpenResp open(Long applicationId, String deviceUniqueId);

    /**
     * 能力支持查询(默认全部支持)
     * 子类根据具体类型重写该方法
     *
     * @return
     */
    public List<AbilityEnum> abilities() {
        return Arrays.asList(AbilityEnum.values());
    }

    /**
     * 车辆授权
     * @param authCarCmd
     */
    public abstract void authCar(AuthCarCmd authCarCmd);

    /**
     * 压线事件处理
     * @param handleReq
     */
    public void crimping(HandleReq handleReq) {
        if (CarAccessRecordConstants.InOrOutEnum.OUT.equals(handleReq.getStateEnum())) {
            // 出闸压线
            DubboServiceExportFactory factory = SpringUtils.getBean(DubboServiceExportFactory.class);
            ParkingOrderService parkingOrderService = factory.getParkingOrderService();
            ParkingCurrentCarService parkingCurrentCarService = factory.getParkingCurrentCarService();
            ParkingCardMemberService parkingCardMemberService = factory.getParkingCardMemberService();
            ParkingBusiness parkingBusiness = factory.getParkingBusiness();

            BigDecimal dueAmount = null;
            ParkingCardMemberDto memberDto = parkingCardMemberService.selectValidMember(handleReq.getDevice().getVillageId()
                    , handleReq.getLicense(), handleReq.getLicenseColorEnum().getLicenseColor());
            if (Objects.nonNull(memberDto)) {
                // 会员车压线，可能需要计算金额
                ParkingCurrentCarDto parkingCurrentCarDto = parkingCurrentCarService.selectOneCar(handleReq.getDevice().getVillageId()
                        , handleReq.getLicense(), handleReq.getLicenseColorEnum().getLicenseColor());
                if (Objects.nonNull(parkingCurrentCarDto)) {
                    ParkingOrderDto parkingOrderDto = parkingOrderService.selectById(parkingCurrentCarDto.getOrderId());
                    Preconditions.checkNotNull(parkingOrderDto);
                    if (ParkingOrderConstants.OrderPayStatusEnum.NO_PAY.getPayStatus().equals(parkingOrderDto.getPayStatus())) {
                        ParkingChargingDto parkingChargingDto = JacksonUtils.parse(parkingOrderDto.getChargingConfBak(), ParkingChargingDto.class);
                        Preconditions.checkNotNull(parkingChargingDto);
                        dueAmount = parkingBusiness.calAmount(parkingChargingDto, parkingOrderDto.getInTime(), handleReq.getEventTime());
                    } else {
                        // 可能是超时订单未支付
                        ParkingOrderDto overtimeOrder = parkingOrderService.selectByPreId(parkingCurrentCarDto.getOrderId());
                        if (Objects.nonNull(overtimeOrder)
                                && ParkingOrderConstants.OrderPayStatusEnum.NO_PAY.getPayStatus().equals(overtimeOrder.getPayStatus())) {
                            ParkingChargingDto overtimeChargingDto = JacksonUtils.parse(overtimeOrder.getChargingConfBak(), ParkingChargingDto.class);
                            Preconditions.checkNotNull(overtimeChargingDto);
                            dueAmount = parkingBusiness.calAmount(overtimeChargingDto, overtimeOrder.getInTime(), handleReq.getEventTime());
                        }
                    }
                }
            }

            CrimpingReqDto.CrimpingReqDtoBuilder builder = CrimpingReqDto.builder();
            builder.deviceId(handleReq.getDevice().getId())
                    .license(handleReq.getLicense())
                    .crimpingTime(handleReq.getEventTime())
                    .licenseColorEnum(handleReq.getLicenseColorEnum())
                    .dueAmount(dueAmount);
            CrimpingCheckRespDto checkRespDto = parkingOrderService.crimping(builder.build());
            if (Boolean.TRUE.equals(checkRespDto.getAllowOpen())
                    && checkRespDto.getOrderId() != null) {
                ParkingGateCmdBusiness parkingGateCmdBusiness = factory.getParkingGateCmdBusiness();
                parkingGateCmdBusiness.open(handleReq.getDevice().getId(), checkRespDto.getOrderId());
            }
        }
    }

    /**
     * 车辆出入事件处理
     *
     * @param handleReq
     * @return
     */
    public void accessHandle(HandleReq handleReq) {
        DeviceDto device = handleReq.getDevice();

        final Date currentDate = new Date();

        DubboServiceExportFactory factory = SpringUtils.getBean(DubboServiceExportFactory.class);
        CarService carService = factory.getCarService();
        CarDto carDto = carService.selectOne(device.getVillageId(), handleReq.getLicense()
                , handleReq.getLicenseColorEnum().getLicenseColor());

        // 车辆频繁出入预警判断
        CarAccessLimitBusiness carAccessLimitBusiness = factory.getCarAccessLimitBusiness();
        PreWarningRecordService preWarningRecordService = factory.getPreWarningRecordService();
        WebSocketComponent webSocketComponent = factory.getWebSocketComponent();
        PreWarningConfigService preWarningConfigService = factory.getPreWarningConfigService();
        PARK_EVENT_HANDLE_POOL.submit(()->{

            PreWarningConfigDto preWarningConfigDto = preWarningConfigService.selectOne(device.getVillageId(), PreWarnConstants.AlarmTypeEnum.CAR_RECORD_TOO_MANY.getType());
            if (Objects.isNull(preWarningConfigDto)
                    || PreWarnConstants.AlarmStatusEnum.DISABLE.getStatus().equals(preWarningConfigDto.getStatus())) {
                return ;
            }

            boolean exceedingLimit = carAccessLimitBusiness.addItemToWindow(device.getVillageId()
                    , handleReq.getLicenseColorEnum().getLicenseColor(), handleReq.getLicense());

            PreWarningRecordDto preWarningRecordDto = preWarningRecordService.selectOneByCar(device.getVillageId()
                    , handleReq.getLicenseColorEnum().getLicenseColor(), handleReq.getLicense());

            if (exceedingLimit) {
                Long items = carAccessLimitBusiness.getItems(device.getVillageId(), handleReq.getLicenseColorEnum().getLicenseColor(), handleReq.getLicense());
                if (items != null) {
                    if (Objects.nonNull(preWarningRecordDto)) {
                        PreWarningRecordDto updateObj = new PreWarningRecordDto();
                        updateObj.setId(preWarningRecordDto.getId());
                        updateObj.setFrequency(items.intValue());
                        preWarningRecordService.updateById(updateObj);
                    } else {
                        // 产生预警
                        PreWarningRecordDto newlyDto = new PreWarningRecordDto();
                        newlyDto.setTenantId(device.getTenantId());
                        newlyDto.setVillageId(device.getVillageId());
                        newlyDto.setObjId(Objects.nonNull(carDto) ? carDto.getId() : null);
                        newlyDto.setLicenseColor(handleReq.getLicenseColorEnum().getLicenseColor());
                        newlyDto.setLicense(handleReq.getLicense());
                        newlyDto.setPreWarnType(PreWarnConstants.AlarmTypeEnum.CAR_RECORD_TOO_MANY.getType());
                        newlyDto.setLastAccessTime(handleReq.getEventTime());
                        newlyDto.setLastSnapImages(handleReq.snapImages);
                        newlyDto.setAlarmTime(currentDate);
                        newlyDto.setCheckTime(currentDate);
                        newlyDto.setConfigId(preWarningConfigDto.getId());
                        newlyDto.setConfigBak(JacksonUtils.toJsonString(preWarningConfigDto));
                        Long save = preWarningRecordService.save(newlyDto);
                        if (save != null) {
                            webSocketComponent.sendMsgToCmsApi(device.getVillageId()
                                    , SendMsgType.PRE_WARN.getMsgTypeFlag()
                                    , SendMsgType.PRE_WARN.getTitle()
                                    , SendMsgType.PRE_WARN.getContentFunc().apply(new String[]{handleReq.getLicense()
                                            , DateUtils.format(currentDate, DateUtils.FormatType.COMMON)
                                            , preWarningConfigDto.getName()})
                                    , null);
                        }
                    }
                }

            } else {
                if (Objects.nonNull(preWarningRecordDto)) {
                    preWarningRecordService.clean(preWarningRecordDto.getId());
                }
            }
        });

        // 车辆进出记录保存
        MongoCarAccessRecordService carAccessRecordService = factory.getCarAccessRecordService();
        PARK_EVENT_HANDLE_POOL.submit(() -> {
            MongoCarAccessRecordDto mongoCarAccessRecordDto = new MongoCarAccessRecordDto();
            mongoCarAccessRecordDto.setOutOrIn(handleReq.getStateEnum().getInOrOut());
            mongoCarAccessRecordDto.setTenantId(device.getTenantId());
            mongoCarAccessRecordDto.setVillageId(device.getVillageId());
            mongoCarAccessRecordDto.setDeviceCategoryId(device.getDeviceCategoryId());
            mongoCarAccessRecordDto.setDeviceTypeId(device.getDeviceTypeId());
            if (Objects.nonNull(carDto)) {
                mongoCarAccessRecordDto.setCarId(carDto.getId());
                mongoCarAccessRecordDto.setCarType(carDto.getCarType());
            }
            mongoCarAccessRecordDto.setDeviceId(device.getId());
            mongoCarAccessRecordDto.setDeviceName(device.getName());
            mongoCarAccessRecordDto.setLicense(handleReq.getLicense());
            mongoCarAccessRecordDto.setLicenseColor(handleReq.getLicenseColorEnum().getLicenseColor());
            mongoCarAccessRecordDto.setSnapImages(handleReq.getSnapImages());
            mongoCarAccessRecordDto.setSnapVideoes(handleReq.getSnapVideoes());
            mongoCarAccessRecordDto.setLongitude(device.getLongitude());
            mongoCarAccessRecordDto.setLatitude(device.getLatitude());
            mongoCarAccessRecordDto.setAccessTime(handleReq.getEventTime());
            carAccessRecordService.save(mongoCarAccessRecordDto);
        });

        // 车辆进入，产生当前停车记录或临停账单，车辆出，清理当前停车记录
        ParkingOrderService parkingOrderService = factory.getParkingOrderService();
        PARK_EVENT_HANDLE_POOL.submit(()->{
            if (handleReq.getStateEnum().equals(CarAccessRecordConstants.InOrOutEnum.IN)) {
                DeviceTypeDto deviceType = device.getDeviceType();
                AbstractIntelligentParkingGateAccessor intelligentParkingGateAccessor = AccessorUtils.getIntelligentParkingGateAccessor(deviceType);
                Preconditions.checkNotNull(intelligentParkingGateAccessor);
                Boolean genOrder = intelligentParkingGateAccessor.abilities().contains(AbilityEnum.PARKING_PAY);

                ParkingReqDto.ParkingReqDtoBuilder builder = ParkingReqDto.builder()
                        .deviceId(device.getId())
                        .license(handleReq.getLicense())
                        .licenseColorEnum(handleReq.getLicenseColorEnum())
                        .inTime(handleReq.getEventTime())
                        .genOrder(genOrder);
                if (Objects.nonNull(handleReq.getCarType())) {
                    builder.carType(handleReq.getCarType());
                }
                if (StringUtils.isNotBlank(handleReq.getSnapImages())) {
                    builder.inPictures(handleReq.getSnapImages());
                }

                parkingOrderService.parkReq(builder.build());
            } else {
                parkingOrderService.clean(new CleanReqDto(device.getId(), handleReq.getLicense(), handleReq.getLicenseColorEnum(), handleReq.getEventTime(), handleReq.getSnapImages()));
            }

        });
    }

    @Data
    public static class HandleReq {
        /**
         * 道闸设备
         */
        @NonNull
        private final DeviceDto device;
        /**
         * 车牌颜色
         */
        @NonNull
        private final CarConstants.LicenseColorEnum licenseColorEnum;
        /**
         * 车牌
         */
        @NonNull
        private final String license;
        /**
         * 进出方式
         */
        @NonNull
        private final CarAccessRecordConstants.InOrOutEnum stateEnum;
        /**
         * 事件事件
         */
        @NonNull
        private final Date eventTime;

        /**
         * 车辆类型 1大车 2小车
         */
        private Byte carType;
        /**
         * 抓拍照片
         */
        private String snapImages;
        /**
         * 抓拍录像
         */
        private String snapVideoes;
    }
}
